<template>
  <div class="about">
    <div id="container">
      <div id="stencil"></div>
      <div id="graph-container"></div>
      <el-button class="export-btn" type="primary" @click="exportImage">导出图片</el-button>
    </div>

  </div>
</template>
<script lang="ts" setup>
import {onMounted, ref} from 'vue'
import {Graph, Shape} from '@antv/x6'
import {Stencil} from '@antv/x6-plugin-stencil'
import {Transform} from '@antv/x6-plugin-transform' // 图形变换
import {Selection} from '@antv/x6-plugin-selection' // 复制粘贴
import {Snapline} from '@antv/x6-plugin-snapline' //对齐线
import {Keyboard} from '@antv/x6-plugin-keyboard' // 快捷键
import {Clipboard} from '@antv/x6-plugin-clipboard' // 复制粘贴
import {History} from '@antv/x6-plugin-history' // 撤销重做
import {Scroller} from '@antv/x6-plugin-scroller'; // 滚动画布
import {Export} from '@antv/x6-plugin-export' // 导出
import {data} from '../assets/data.js';

onMounted(() => {
  initGraph()
})

let graph: Graph;
const initGraph = () => {
  // 初始化画布
  graph = new Graph({
    container: document.getElementById('graph-container')!,
    grid: true,
    mousewheel: {
      enabled: true,
      zoomAtMousePosition: true,
      modifiers: 'ctrl',
      minScale: 0.5,
      maxScale: 3,
    },
    connecting: {
      router: 'manhattan',
      connector: {
        name: 'rounded',
        args: {
          radius: 8,
        },
      },
      anchor: 'center',
      connectionPoint: 'anchor',
      allowBlank: false,
      snap: {
        radius: 20,
      },
      createEdge() {
        return new Shape.Edge({
          attrs: {
            line: {
              stroke: '#A2B1C3',
              strokeWidth: 2,
              targetMarker: {
                name: 'block',
                width: 12,
                height: 8,
              },
            },
          },
          zIndex: 0,
          tools: [
            {
              name: 'edge-editor',
              args: {
                attrs: {
                  backgroundColor: '#fff',
                },
              },
            },
          ]
        })
      },
      validateConnection({targetMagnet}) {
        return !!targetMagnet
      },
    },
    highlighting: {
      magnetAdsorbed: {
        name: 'stroke',
        args: {
          attrs: {
            fill: '#5F95FF',
            stroke: '#5F95FF',
          },
        },
      },
    },
  })

  // 使用插件
  graph
    .use(
      new Transform({
        resizing: false,
        rotating: false,
      }),
    )
    .use(
      new Selection({
        rubberband: true,
        showNodeSelectionBox: true,
      }),
    )
    .use(new Snapline())
    .use(new Keyboard())
    .use(new Clipboard())
    .use(new History())
    .use(new Export())
    .use(new Scroller({
      enabled: true,
    }))

  // #region 快捷键与事件
  graph.bindKey(['meta+c', 'ctrl+c'], () => {
    const cells = graph.getSelectedCells()
    if (cells.length) {
      graph.copy(cells)
    }
    return false
  })
  graph.bindKey(['meta+x', 'ctrl+x'], () => {
    const cells = graph.getSelectedCells()
    if (cells.length) {
      graph.cut(cells)
    }
    return false
  })
  graph.bindKey(['meta+v', 'ctrl+v'], () => {
    if (!graph.isClipboardEmpty()) {
      const cells = graph.paste({offset: 32})
      graph.cleanSelection()
      graph.select(cells)
    }
    return false
  })

  // undo redo
  graph.bindKey(['meta+z', 'ctrl+z'], () => {
    if (graph.canUndo()) {
      graph.undo()
    }
    return false
  })
  graph.bindKey(['meta+shift+z', 'ctrl+shift+z'], () => {
    if (graph.canRedo()) {
      graph.redo()
    }
    return false
  })

  // select all
  graph.bindKey(['meta+a', 'ctrl+a'], () => {
    const nodes = graph.getNodes()
    if (nodes) {
      graph.select(nodes)
    }
  })

  // delete
  graph.bindKey('backspace', () => {
    const cells = graph.getSelectedCells()
    if (cells.length) {
      graph.removeCells(cells)
    }
  })

  // zoom
  graph.bindKey(['ctrl+1', 'meta+1'], () => {
    const zoom = graph.zoom()
    if (zoom < 1.5) {
      graph.zoom(0.1)
    }
  })
  graph.bindKey(['ctrl+2', 'meta+2'], () => {
    const zoom = graph.zoom()
    if (zoom > 0.5) {
      graph.zoom(-0.1)
    }
  })
  // #endregion

  // 控制连接桩显示/隐藏
  const showPorts = (ports: NodeListOf<SVGElement>, show: boolean) => {
    for (let i = 0, len = ports.length; i < len; i += 1) {
      ports[i].style.visibility = show ? 'visible' : 'hidden'
    }
  }
  graph.on('node:mouseenter', () => {
    const container = document.getElementById('graph-container')!
    const ports = container.querySelectorAll(
      '.x6-port-body',
    ) as NodeListOf<SVGElement>
    showPorts(ports, true)
  })
  graph.on('node:mouseleave', () => {
    const container = document.getElementById('graph-container')!
    const ports = container.querySelectorAll(
      '.x6-port-body',
    ) as NodeListOf<SVGElement>
    showPorts(ports, false)
  })

  graph.on('node:added', ({node}) => {
    console.log('node:added', node);
    node.resize(node.data.width, node.data.height);
    node.attr('image/width', node.data.width)
    node.attr('image/height', node.data.height)
  });

  let clickTimeout: ReturnType<typeof setTimeout> | null = null;
  graph.on('edge:click', ({e, x, y, edge, view}) => {
    // 防止在双击时触发单击
    if (clickTimeout) {
      clearTimeout(clickTimeout)
      clickTimeout = null
      return
    }

    // 单击逻辑：延迟执行，给双击留出时间
    clickTimeout = setTimeout(() => {
      console.log('Single click')
      clickTimeout = null;

      if (edge.attrs['line'].strokeDasharray=== undefined) {
        edge.attr('line/strokeDasharray', 5)
      } else if(edge.attrs['line'].strokeDasharray==5) {
        edge.attr('line/strokeDasharray', 0);
        edge.attr('line/targetMarker', 'classic')
        edge.attr('line/stroke', '#f5222d')
      } else if(edge.attrs['line'].strokeDasharray===0) {
        edge.attr('line/targetMarker', 'block')
        edge.attrs['line'].strokeDasharray= undefined;
        edge.attr('line/stroke', '#A2B1C3')
      }

    }, 300) // 超时时间通常设为 250~300ms
  })

  graph.on('edge:dblclick', ({e, x, y, edge, view}) => {
    e.preventDefault()
    // 双击时清除单击定时器
    if (clickTimeout) {
      clearTimeout(clickTimeout)
      clickTimeout = null
    }
  })

  // 初始化图形
  const ports = {
    groups: {
      top: {
        position: 'top',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#5F95FF',
            strokeWidth: 1,
            fill: '#fff',
            style: {
              visibility: 'hidden',
            },
          },
        },
      },
      right: {
        position: 'right',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#5F95FF',
            strokeWidth: 1,
            fill: '#fff',
            style: {
              visibility: 'hidden',
            },
          },
        },
      },
      bottom: {
        position: 'bottom',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#5F95FF',
            strokeWidth: 1,
            fill: '#fff',
            style: {
              visibility: 'hidden',
            },
          },
        },
      },
      left: {
        position: 'left',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#5F95FF',
            strokeWidth: 1,
            fill: '#fff',
            style: {
              visibility: 'hidden',
            },
          },
        },
      },
    }
  }

  Graph.registerNode(
    'custom-image',
    {
      inherit: 'rect',
      width: 120,  // 图例宽度
      height: 120, // 图例高度
      markup: [
        {
          tagName: 'rect',
          selector: 'body',
        },
        {
          tagName: 'image',
        },
        {
          tagName: 'text',
          selector: 'label',
        },
      ],
      attrs: {
        body: {
          strokeWidth: 0, // 边框宽度
          fill: 'none',
        },
        image: {
          width: 120, // 图片宽度
          height: 100, // 图片高度
          refX: 2,
          refY: 20,
        },
        label: {
          refX: 3,
          refY: 3,
          textAnchor: 'left',
          textVerticalAnchor: 'top',
          fontSize: 12,
          fill: '#000', // 文字颜色
        },
      },
      ports: {...ports},
    },
    true,
  )
  Graph.registerNode(
    'custom-rect',
    {
      inherit: 'rect',
      width: 120,
      height: 40,
      attrs: {
        body: {
          strokeWidth: 1,
          stroke: '#5F95FF',
          fill: '#EFF4FF',
        },
        text: {
          fontSize: 14,
          fill: '#262626',
        },
      },
      ports: {...ports},
    },
    true,
  )

  const imageShapes = data;
  const imageNodes = imageShapes.map((item: any) =>
    graph.createNode({
      shape: 'custom-image',
      label: item.label,
      attrs: {
        image: {
          'xlink:href': item.image,
        },
      },
      ports: item.ports,
      data: item.data,
      tools: [
        {
          name: 'node-editor',
          args: {
            x:0,
            y:0,
            attrs: {
              backgroundColor: '#EFF4FF',
            },
          },
        },
      ]
    }),
  )
  const t1 = graph.createNode({
    shape: 'custom-rect',
    label: '文本框',
    data: {
      width: 120,
      height: 40,
    },
    ports: {
      items: [
        {
          group: 'top',
        },
        {
          group: 'right',
        },
        {
          group: 'bottom',
        },
        {
          group: 'left',
        },
      ]
    },
    tools: [
      {
        name: 'node-editor',
        args: {
          attrs: {
            backgroundColor: '#EFF4FF',
          },
        },
      },
    ]
  })

  // 初始化 stencil
  const stencil = new Stencil({
    title: '用户报装方案元件',
    target: graph,
    stencilGraphWidth: 200,
    stencilGraphHeight: 180,
    collapsable: true,// 是否显示全局折叠/展开按钮。
    groups: [
      {
        title: '中压报装方案生成元件',
        name: 'group1',
        graphHeight: 900,
        layoutOptions: {
          rowHeight: 140,
        },
      },
      {
        title: '文本编辑框',
        name: 'group2',
        graphHeight: 100,
        layoutOptions: {
          rowHeight: 40,
        },
      },
    ],
    layoutOptions: {
      columns: 1,
      columnWidth: 'auto',
      rowHeight: 'auto',
      resizeToFit: true,
    }
  })
  document.getElementById('stencil')!.appendChild(stencil.container);

  stencil.load(imageNodes, 'group1');
  stencil.load([t1], 'group2')
}

const exportImage = async () => {
  // 导出图片直接下载
  // const fileName = Date.parse(new Date().toString()).toString() + '.jpg'
  // graph.exportJPEG(fileName, { // exportPNG
  //   width: 800,
  //   height: 600,
  //   backgroundColor: '#fff',
  //   padding: 50,
  //   quality: 1 // 图片质量，可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围，将会使用默认值 0.92
  // })

  // 导出图片base64转成blob再转成File上传
  const obj ={
    width: 800,
    height: 600,
    backgroundColor: '#fff',
    padding: 50,
    quality: 1 // 图片质量，可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围，将会使用默认值 0.92

  }
  await graph.toJPEG((base64Data)=>{
    base64ToBlob(base64Data).then(blob => {
      // 创建具有唯一文件名的 File 对象
      const fileName = `${Date.now()}-${Math.round(Math.random() * 1e6)}.jpg`;
      const file = new File([blob], fileName, { type: 'image/jpeg' });
      console.log(file)
    });
  },obj);
}

function base64ToBlob(base64: string): Promise<Blob> {
  return fetch(base64).then(res => res.blob());
}


</script>
<style>
#container {
  display: flex;
  height: 100vh;
  position: relative;
}

#container .export-btn {
  position: absolute;
  right: 20px;
  top: 20px;
}

#stencil {
  width: 180px;
  height: 100%;
  position: relative;
  border-right: 1px solid #dfe3e8;
}

#graph-container {
  width: calc(100% - 180px);
  height: 100%;
}

.x6-widget-stencil {
  background-color: #fff;
}

.x6-widget-stencil-title {
  background-color: #fff;
}

.x6-widget-stencil-group-title {
  background-color: #fff !important;
}

.x6-widget-transform {
  margin: -1px 0 0 -1px;
  padding: 0px;
  border: 1px solid #239edd;
}

.x6-widget-transform > div {
  border: 1px solid #239edd;
}

.x6-widget-transform > div:hover {
  background-color: #3dafe4;
}

.x6-widget-transform-active-handle {
  background-color: #3dafe4;
}

.x6-widget-transform-resize {
  border-radius: 0;
}

.x6-widget-selection-inner {
  border: 1px solid #239edd;
}

.x6-widget-selection-box {
  opacity: 0;
}
.x6-edge-editor-input {
  border: 1px solid #ccc;
  padding: 4px 8px;
  border-radius: 4px;
  font-size: 12px;
}
</style>
